#include "modules/WifiControl.h"
#include <cstring>
#include "util.h"
#include <sstream>

#include "Coordinator.h"

enum msg_cmd {
    MSG_CMD_ROUTER_CONFIG,
    MSG_CMD_CLIENT_CONFIG,
    MSG_CMD_USB_OFF,
    MSG_CMD_USB_ON
};

#define IFACE_NAME "wlan0"
#define CLI_WLAN_SCAN "sudo iwlist " IFACE_NAME " scan"
#define CLI_WLAN_STATE "ip link show " IFACE_NAME
#define CLI_WLAN_UP "ifup " IFACE_NAME
#define CLI_WLAN_DOWN "ifdown " IFACE_NAME
#define CLI_PING_GOOGLE "ping -c1 google.com"

#define WAITING_FOR_CONFIG_TIMEOUT_MSEC 30000

void WifiControl::init(){
    loadXML_wifi_credential();
    m_led = std::shared_ptr<LedControl>(new LedControl(GPIO_FRONT_LED, 1500, 50));
    m_led->start();
    m_led->set_on_no_blink();
    m_should_retry_connect = false;
}

int WifiControl::threadHandler(){
    LOG << "starting WifiControl thread handler" << std::endl;
    bool waiting_for_usb_on = false;
    bool waiting_for_net_config = false;
    std::chrono::high_resolution_clock::time_point 
        waiting_for_net_config_tp = std::chrono::high_resolution_clock::now();
    
    std::shared_ptr<Message> m = 0;
    while (m_shouldRun){
        //check if any message waiting in queue
        m = m_q.deque();
        //TODO ensure that state mode dequeued is the last
        while (0 != m && m_shouldRun){
            if (MSG_CMD_ROUTER_CONFIG == m->cmd){
                ////TODO same as client with waiting_for_usb_on
                m_led->set_dutty_cycle(20);
                m_led->enable_blink();
                waiting_for_net_config = true;
                waiting_for_net_config_tp = std::chrono::high_resolution_clock::now();
                
                do_router_config();
                
                //notify coordinator
                m_coordinator->enqueMessage(std::shared_ptr<Message> (new Message(msg::CREATED_WIFI_NET)));
            }else if (MSG_CMD_CLIENT_CONFIG == m->cmd){
                if (! wireless_is_enabled()){
                    //wireless is disabled, repost client config message
                    set_as_client();
                    waiting_for_usb_on = true;
                }else{
                    m_led->set_dutty_cycle(20);
                    m_led->enable_blink();
                    if (do_client_config()){
                        waiting_for_net_config = true;
                        waiting_for_net_config_tp = std::chrono::high_resolution_clock::now();
                    }else{
                        //no know network found (or problem accessing file), retry
                        m_should_retry_connect = true;
                    }
                    
                }
                
            }else if (MSG_CMD_USB_OFF == m->cmd){
                LOG << "      USB OFF" << std::endl;
                m_led->set_off_no_blink();
                Util::exec_read_bash("echo 0x0 > /sys/devices/platform/bcm2708_usb/buspower");
                waiting_for_usb_on = false;
            }else if (MSG_CMD_USB_ON == m->cmd){
                LOG << "      USB ON" << std::endl;
                m_led->set_dutty_cycle(80);
                m_led->enable_blink();
                Util::exec_read_bash("echo 0x1 > /sys/devices/platform/bcm2708_usb/buspower");
                waiting_for_usb_on = true;
            }
            m = m_q.deque();
        }
        
        //confirm wifi state with led
        if (waiting_for_usb_on){
            if (wireless_is_enabled()){
                //wlan0 is back up
                LOG << "USB is ON !!!!!!!!!" << std::endl;
                waiting_for_usb_on = false;
            }
        }else if (waiting_for_net_config){// && ! waiting_for_usb_on){
            if (local_net_status_connected()){
                m_led->set_off_no_blink();
                waiting_for_net_config = false;
                //notify coordinator
                m_coordinator->enqueMessage(std::shared_ptr<Message> (new Message(msg::JOINED_WIFI_NET)));
            }else{
                //check if waiting for config timed out
                std::chrono::high_resolution_clock::time_point now = std::chrono::high_resolution_clock::now();
                std::chrono::milliseconds d = 
                    std::chrono::duration_cast<std::chrono::milliseconds>
                    (now - waiting_for_net_config_tp)
                ;
                if (d.count() > WAITING_FOR_CONFIG_TIMEOUT_MSEC){
                    //waited long enough, give up
                    m_led->set_on_no_blink();
                    waiting_for_net_config = false;
                    //nope ! retry !
                    m_should_retry_connect = true;
                }
            }
        }
        if (m_should_retry_connect){
            m_should_retry_connect = false; 
            set_as_client();
        }
        //no need to sleep less than 500msec
        std::this_thread::sleep_for(std::chrono::milliseconds(500));
    }

    LOG << "Quitting WifiControl::threadHandler... 2" << std::endl;
    return 0;
}

void WifiControl::set_as_router(){
    enqueMessage(std::shared_ptr<Message> (new Message(MSG_CMD_ROUTER_CONFIG)));
}

void WifiControl::set_as_client(){
    enqueMessage(std::shared_ptr<Message> (new Message(MSG_CMD_CLIENT_CONFIG)));
}

void WifiControl::wireless_disable(){
    enqueMessage(std::shared_ptr<Message> (new Message(MSG_CMD_USB_OFF)));
}

void WifiControl::wireless_enable(){
    enqueMessage(std::shared_ptr<Message> (new Message(MSG_CMD_USB_ON)));
}

bool WifiControl::wireless_is_enabled(){
    std::string ret = Util::exec_read_bash("ifconfig | grep -c " IFACE_NAME);
    //LOG << Util::exec_read_bash("ifconfig") << std::endl;
    return (0 == ret.compare("1\n"));
}

bool WifiControl::local_net_status_connected(){
    std::string ret = Util::exec_read_bash(CLI_WLAN_STATE);
    int ind = ret.find("UP");
    return ind != std::string::npos;
}

bool WifiControl::ping_google(){
    //WARNING this function takes lots of time !!!!!
    std::string ret = Util::exec_read_bash(CLI_PING_GOOGLE);
    int ind = ret.find("0% packet loss");
    //if not string found, no internet_access
    return ind != std::string::npos;
}

void WifiControl::do_router_config(){
    LOG << "DO ROUTER CONFIG" << std::endl;
    //bring iface down
    Util::exec_read_bash(CLI_WLAN_DOWN);
    //change /etc/network/interfaces file
    Util::exec_read_bash("cp -f ../data/interfaces_router_template /etc/network/interfaces");
    //bring iface up
    Util::exec_read_bash(CLI_WLAN_UP);
    //start hostapd, wifi access point
    Util::exec_read_bash("/etc/init.d/hostapd stop");
    Util::exec_read_bash("/etc/init.d/hostapd start");
    //start udhcpd, dhcp server
    Util::exec_read_bash("/etc/init.d/udhcpd stop");
    Util::exec_read_bash("/etc/init.d/udhcpd start");
}


bool WifiControl::do_client_config(){
    LOG << "DO CLIENT CONFIG" << std::endl;
    bool ret = true;
    //stop hostapd, wifi access point
    Util::exec_read_bash("/etc/init.d/hostapd stop");
    //stop udhcpd, dhcp server
    Util::exec_read_bash("/etc/init.d/udhcpd stop");
    
    loadXML_wifi_credential();

    ////scan available wifi networks, assume it(s sorted by signal strength
    //an check if credentials are in data store
    std::string ssid, pass;
    bool found_known_network = false;
    std::string ret_scan = Util::exec_read_bash(CLI_WLAN_SCAN);
    int ind = ret_scan.find("ESSID");
    while (std::string::npos != ind){
        int ind_end_line = ret_scan.find("\n", ind);
        //ESSID:" length 7, trailing " -1
        std::string ssid_tmp = ret_scan.substr(ind + 7, ind_end_line - ind - 7 - 1);
        //check if has credential for this ESSID
        for (int i=0; i<m_credential_list.size(); i++){
            LOG << "Searching : found ssid : " << ssid_tmp << "  --- known : " << m_credential_list[i]->ssid << std::endl;
            if (m_credential_list[i]->ssid == ssid_tmp){
                ssid = m_credential_list[i]->ssid;
                pass = m_credential_list[i]->pass;
                found_known_network = true;
                LOG << "found ESSID in credential list, connecting to " << ssid << std::endl;
            }
        }
        ind = ret_scan.find("ESSID", ind_end_line);
    }
    
    if (! found_known_network){
        LOG << "no known network found, so not configuring the pi with bad interfaces file "
            << std::endl;
        ret = false;
        m_coordinator->enqueMessage(std::shared_ptr<Message> (new Message(msg::NO_KNOWN_ESSID)));
    }else{
        ////change /etc/network/interfaces file
        //open client template
        std::string line;
        std::ifstream src ("../data/interfaces_client_template");
        //std::ofstream dst ("../data/interfaces_client_template_test", std::ios::trunc);
        std::ofstream dst ("/etc/network/interfaces", std::ios::trunc);
        if (!src.is_open()){
            LOG << "Unable to open template file interfaces_client_template" << std::endl;
            ret = false;
        }else if (!dst.is_open()){
            src.close();
            LOG << "Unable to open file /etc/network/interfaces" << std::endl;
            ret = false;
        }else{

            while ( getline (src,line) ){
                //TODO get credential from XML file instead
                if (0 == line.compare(0, 8, "wpa-ssid")){
                    line = line + " " + ssid;
                }else if (0 == line.compare(0, 8, "wpa-psk")){
                    line = line + " " + pass;
                }
                dst << line << std::endl;
                //LOG << line << '\n';
            }
            src.close();
            dst.close();
            LOG << "setting iface,..." << std::endl;
            Util::exec_read_bash(CLI_WLAN_DOWN);
            Util::exec_read_bash(CLI_WLAN_UP);
        }
    }
    return ret;
}

void WifiControl::loadXML_wifi_credential(){
    std::string path = ((Coordinator*)(m_coordinator))->m_wifi_credential_list_path;
    LOG << "loading credential list from " << path << std::endl;
    WifiCredentialDataStore ds(path);
    ds.load();
    
    while (! ds.m_credential_list.empty()){
        m_credential_list.push_back(ds.m_credential_list.front());
        //debug
        //LOG << ds.m_credential_list.front()->ssid << " - " << ds.m_credential_list.front()->pass << std::endl;
        ds.m_credential_list.pop_front();
    }
}

